﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;

using NLite.Collections;
using NLite.ComponentModel;
using NLite.Reflection;

namespace NLite.Mapping.Internal
{
    [DebuggerDisplay("{From.Name}->{To.Name}")]
    struct MemberMapping
    {
        public MappingItem From;
        public MappingItem To;
        public string FromMemberPath;
    }

    [DebuggerDisplay("{Name}")]
    struct MappingItem
    {
        public Type Type;
        public MemberInfo Member;

        private Action<object, object> setMember;
        public Action<object, object> SetMember
        {
            get
            {
                if (setMember == null)
                    setMember = Member.GetSetter<object, object>();
                return setMember;
            }
        }

        private Func<object, object> getMember;
        public Func<object, object> GetMember
        {
            get
            {
                if (getMember == null)
                    getMember = Member.GetGetter<object>();
                return getMember;
            }
        }

        public string Name
        {
            get
            {
                return string.Format("[{0} {1}]", Type.FullName, Member.Name);
            }
        }

        public override string ToString()
        {
            return Name;
        }
    }

    sealed class MapperInfo : IMapperInfo
    {
        const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public;

        public Type From { get; internal set; }
        public Type To { get; internal set; }
        internal string Key;

        public bool IgnoreCase { get; internal set; }
        public bool IgnoreUnderscore { get; internal set; }
        public string[] IgnoreSourceMembers { get { return ignoreSourceMembers == null ? new string[0] : ignoreSourceMembers.ToArray(); } }
        public string[] IgnoreDestinationMembers { get { return ignoreDestinationMembers == null ? new string[0] : ignoreDestinationMembers.ToArray(); } }

        internal Dictionary<MemberInfo, Delegate> memberMappings;
        internal HashSet<string> ignoreSourceMembers;
        internal HashSet<string> ignoreDestinationMembers;
        internal Dictionary<string, Delegate> converters;

        public MapperInfo()
        {
            IgnoreCase = true;
            IgnoreUnderscore = true;
        }

        internal bool CanUsingConverter(string key)
        {
            return converters != null &&　converters.ContainsKey(key) && converters[key] != null;
        }

        internal Func<string, string, bool> membersMatcher;

        Lazy<List<MemberMapping>> mappings;

        internal Lazy<List<MemberMapping>> Mappings
        {
            get
            {
                if (mappings != null)
                    return mappings;

                mappings = new Lazy<List<MemberMapping>>(() =>
                    {
                        var fromMembers = SourceMembers.Value;
                        var items = new List<MemberMapping>();

                        foreach (var toMember in DestinationMembers.Value)
                        {
                            string fromMemberPath = null;
                            MemberInfo fromMember = fromMembers.FirstOrDefault(m => IsMatchMember(m, toMember, ref fromMemberPath));
                            if (fromMember == null)
                                continue;

                            if (!string.IsNullOrEmpty(fromMemberPath))
                                fromMemberPath = fromMemberPath.Remove(0, 1);

                            items.Add(new MemberMapping
                            {
                                FromMemberPath = fromMemberPath,
                                From = GetMappingItem(fromMember, true),
                                To = GetMappingItem(toMember, false)
                            });
                        }

                        return items;
                    });

                return mappings;
            }
        }

        Lazy<List<MemberInfo>> sourceMembers;
        internal Lazy<List<MemberInfo>> SourceMembers
        {
            get
            {
                if (sourceMembers != null)
                    return sourceMembers;
                sourceMembers = new Lazy<List<MemberInfo>>(() =>
                    {
                        var ignoreFromMembers = IgnoreSourceMembers;

                        return From
                           .GetFields(bindingFlags)
                           .Where(f => !IsMatchIgnoreMember(f.Name, ignoreFromMembers))
                        	.Where(p=>!p.HasAttribute<IgnoreAttribute>(true))
                           .Cast<MemberInfo>()
                           .Union(From
                               .GetProperties(bindingFlags)
                               .Where(p => p.CanRead && !IsMatchIgnoreMember(p.Name, ignoreFromMembers))
                               .Where(p=>!p.HasAttribute<IgnoreAttribute>(true))
                               .Cast<MemberInfo>()
                               )
                           .Union(From
                               .GetMethods(bindingFlags)
                               .Where(p=>!p.HasAttribute<IgnoreAttribute>(true))
                               .Where(p => p.ReturnType != Types.Void)
                               .Where(p => !p.Name.StartsWith("get_"))
                               .Where(p => !p.Name.StartsWith("set_"))
                               .Where(p => p.Name.StartsWith("Get"))
                               .Where(p => p.Name != "GetHashCode" && p.Name != "GetType")
                               .Cast<MemberInfo>())
                               .ToList();
                    });

                return sourceMembers;
            }
        }

        Lazy<List<MemberInfo>> destinationMembers;
        internal Lazy<List<MemberInfo>> DestinationMembers
        {
            get
            {
                if (destinationMembers != null)
                    return destinationMembers;
                if (memberMappings == null)
                    memberMappings = new Dictionary<MemberInfo, Delegate>(0);

                destinationMembers = new Lazy<List<MemberInfo>>(() =>
                    {
                        var ignoreToMembers = IgnoreDestinationMembers
                           .Union(memberMappings.Keys.ConvertAll<MemberInfo, string>(m => m.Name))
                           .ToArray();

                        return To
                            .GetFields(bindingFlags)
                            .Where(f => !f.IsInitOnly)//确保可写
                            .Where(f => !IsMatchIgnoreMember(f.Name, ignoreToMembers))
                        	.Where(p=>!p.HasAttribute<IgnoreAttribute>(true))
                            .Cast<MemberInfo>()
                            .Union(To
                                .GetProperties(bindingFlags)
                                .Where(p => p.CanWrite && !IsMatchIgnoreMember(p.Name, ignoreToMembers))
                                .Where(p=>!p.HasAttribute<IgnoreAttribute>(true))
                                .Cast<MemberInfo>()
                                )
                            .Union(To
                               .GetMethods(bindingFlags)
                               .Where(p=>!p.HasAttribute<IgnoreAttribute>(true))
                               .Where(p => p.ReturnType == Types.Void)
                               .Where(p => !p.Name.StartsWith("get_"))
                               .Where(p => !p.Name.StartsWith("set_"))
                               .Where(p => p.Name.StartsWith("Set"))
                               .Cast<MemberInfo>())
                             .ToList();
                    });

                return destinationMembers;
            }
        }

        private static MappingItem GetMappingItem(MemberInfo m,bool isFrom)
        {
            switch (m.MemberType)
            {
                case MemberTypes.Field:
                    var field = m as FieldInfo;
                    return new MappingItem { Member = field, Type = field.FieldType };
                case MemberTypes.Property:
                    var prop = m as PropertyInfo;
                    return new MappingItem { Member = isFrom ? prop.GetGetMethod() : prop.GetSetMethod(), Type = prop.PropertyType };
                case MemberTypes.Method:
                    var method = m as MethodInfo;
                    return new MappingItem { Member = method, Type = isFrom ? method.ReturnType : method.GetParameterTypes()[0] };
            }

            return new MappingItem();
        }

        private bool IsMatchMember(MemberInfo fromMember, MemberInfo toMember,ref string fromMemberPath)
        {
            var isFromFieldOrProperty = fromMember.MemberType == MemberTypes.Field || fromMember.MemberType == MemberTypes.Property;
            var isToFieldOrProperty = toMember.MemberType == MemberTypes.Field || toMember.MemberType == MemberTypes.Property;

            var fromName = fromMember.Name.Replace("get_",string.Empty).Replace("Get",string.Empty);
            var toName = toMember.Name.Replace("set_",string.Empty).Replace("Set",string.Empty);

            var flag = IsMatchMemberName(fromName, toName);
            if (flag)
                return true;

            flag = IsMatchFlattingMember(fromMember.GetMemberType(), fromName, toName, ref fromMemberPath);
            if (flag)
                return true;

            if (membersMatcher != null)
                return membersMatcher(fromName, toName);

            return false;
        }

        private bool IsMatchFlattingMember(Type fromType, string fromName, string toName, ref string fromMemberPath)
        {
            if (!toName.StartsWith(fromName))
                return false;

            fromMemberPath = fromMemberPath + "." + fromName;

            toName = toName.Remove(0, fromName.Length);
            const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
            
            foreach(var f in fromType.GetFields(flags).Where(p=>!p.HasAttribute<IgnoreAttribute>(true)))
            {
                fromName = f.Name;
                if (IsMatchMemberName(f.Name, toName))
                {
                    fromMemberPath = fromMemberPath + "." + fromName;
                    return true;
                }
                if (IsMatchFlattingMember(f.FieldType, fromName, toName,ref fromMemberPath))
                    return true;
            }

            foreach (var f in fromType.GetProperties(flags).Where(p=>!p.HasAttribute<IgnoreAttribute>(true)))
            {
                fromName = f.Name;
                if (IsMatchMemberName(f.Name, toName))
                {
                    fromMemberPath = fromMemberPath + "." + fromName;
                    return true;
                }
                if (IsMatchFlattingMember(f.PropertyType, fromName, toName, ref fromMemberPath))
                    return true;
            }

            foreach (var f in fromType.GetMethods(flags).Where(m=>m.Name.StartsWith("Get") && m.ReturnType != Types.Void && !m.HasAttribute<IgnoreAttribute>(true)))
            {
                fromName = f.Name;
                if (IsMatchMemberName(f.Name, toName))
                {
                    fromMemberPath = fromMemberPath + "." + fromName;
                    return true;
                }
                if (IsMatchFlattingMember(f.ReturnType, fromName, toName, ref fromMemberPath))
                    return true;
            }

            return false;
        }

        private bool IsMatchMemberName(string fromName, string toName)
        {
            if (IgnoreUnderscore)
                return string.Compare(
                         fromName.Replace("_", string.Empty),
                         toName.Replace("_", string.Empty),
                         IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) == 0;

            return string.Compare(
                    fromName,
                    toName,
                    IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) == 0;
        }

        private bool IsMatchIgnoreMember(string fieldName,string[] ignoreMembers)
        {

            var stringComparision = IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
            fieldName = IgnoreUnderscore ? fieldName.Replace("_", string.Empty) : fieldName;

            foreach (string ignoreField in ignoreMembers)
            {
                if (string.Compare(fieldName, ignoreField, stringComparision) == 0)
                    return true;
            }

            return false;
        }

        private string[] StringArrayFilter(string[] array)
        {
            if (IgnoreUnderscore)
            {
                int length = array.Length;
                for (int i = 0; i < length; i++)
                    array[i] = array[i].Replace("_", string.Empty);
            }

            return array;
        }
    }

}
